home *** CD-ROM | disk | FTP | other *** search
/ Total Network Tools 2002 / NextStepPublishing-TotalNetworkTools2002-Win95.iso / Archive / Misc Servers / Zope.exe / HTTP_SERVER.PY < prev    next >
Encoding:
Python Source  |  2000-08-23  |  20.5 KB  |  774 lines

  1. #! /usr/local/bin/python
  2. # -*- Mode: Python; tab-width: 4 -*-
  3. #
  4. #    Author: Sam Rushing <rushing@nightmare.com>
  5. #    Copyright 1996-2000 by Sam Rushing
  6. #                         All Rights Reserved.
  7. #
  8.  
  9. RCS_ID =  '$Id: http_server.py,v 1.16.4.3 2000/08/23 20:38:42 brian Exp $'
  10.  
  11. # python modules
  12. import os
  13. import regex
  14. import re
  15. import socket
  16. import stat
  17. import string
  18. import sys
  19. import time
  20.  
  21. # async modules
  22. import asyncore
  23. import asynchat
  24.  
  25. # medusa modules
  26. import http_date
  27. import producers
  28. import status_handler
  29. import logger
  30.  
  31. VERSION_STRING = string.split(RCS_ID)[2]
  32.  
  33. from counter import counter
  34.  
  35. # ===========================================================================
  36. #                            Request Object
  37. # ===========================================================================
  38.  
  39. class http_request:
  40.  
  41.     # default reply code
  42.     reply_code = 200
  43.  
  44.     request_counter = counter()
  45.  
  46.     # Whether to automatically use chunked encoding when
  47.     # 
  48.     #   HTTP version is 1.1
  49.     #   Content-Length is not set
  50.     #   Chunked encoding is not already in effect
  51.     #
  52.     # If your clients are having trouble, you might want to disable this.
  53.     use_chunked = 1
  54.     
  55.     # by default, this request object ignores user data.
  56.     collector = None
  57.  
  58.     def __init__ (self, *args):
  59.         # unpack information about the request
  60.         (self.channel, self.request,
  61.          self.command, self.uri, self.version,
  62.          self.header) = args
  63.  
  64.         self.outgoing = fifo()
  65.         self.reply_headers = {
  66.             'Server'    : 'Medusa/%s' % VERSION_STRING,
  67.             'Date'        : http_date.build_http_date (time.time())
  68.             }
  69.         self.request_number = http_request.request_counter.increment()
  70.         self._split_uri = None
  71.         self._header_cache = {}
  72.  
  73.     # --------------------------------------------------
  74.     # reply header management
  75.     # --------------------------------------------------
  76.     def __setitem__ (self, key, value):
  77.         self.reply_headers[key] = value
  78.  
  79.     def __getitem__ (self, key):
  80.         return self.reply_headers[key]
  81.  
  82.     def has_key (self, key):
  83.         return self.reply_headers.has_key (key)
  84.  
  85.     def build_reply_header (self):
  86.         return string.join (
  87.             [self.response(self.reply_code)] + map (
  88.                 lambda x: '%s: %s' % x,
  89.                 self.reply_headers.items()
  90.                 ),
  91.             '\r\n'
  92.             ) + '\r\n\r\n'
  93.  
  94.     # --------------------------------------------------
  95.     # split a uri
  96.     # --------------------------------------------------
  97.  
  98.     # <path>;<params>?<query>#<fragment>
  99.     path_regex = regex.compile (
  100.     #        path        params        query       fragment
  101.         '\\([^;?#]*\\)\\(;[^?#]*\\)?\\(\\?[^#]*\)?\(#.*\)?'
  102.         )
  103.  
  104.     def split_uri (self):
  105.         if self._split_uri is None:
  106.             if self.path_regex.match (self.uri) != len(self.uri):
  107.                 raise ValueError, "Broken URI"
  108.             else:
  109.                 self._split_uri = map (lambda i,r=self.path_regex: r.group(i), range(1,5))
  110.         return self._split_uri
  111.  
  112.  
  113.     def get_header_with_regex (self, head_reg, group):
  114.         for line in self.header:
  115.             if head_reg.match (line) == len(line):
  116.                 return head_reg.group(group)
  117.         return ''
  118.  
  119.     def get_header (self, header):
  120.         header = string.lower (header)
  121.         hc = self._header_cache
  122.         if not hc.has_key (header):
  123.             h = header + ': '
  124.             hl = len(h)
  125.             for line in self.header:
  126.                 if string.lower (line[:hl]) == h:
  127.                     r = line[hl:]
  128.                     hc[header] = r
  129.                     return r
  130.             hc[header] = None
  131.             return None
  132.         else:
  133.             return hc[header]
  134.  
  135.     # --------------------------------------------------
  136.     # user data
  137.     # --------------------------------------------------
  138.  
  139.     def collect_incoming_data (self, data):
  140.         if self.collector:
  141.             self.collector.collect_incoming_data (data)
  142.         else:
  143.             self.log_info(
  144.                 'Dropping %d bytes of incoming request data' % len(data),
  145.                 'warning'
  146.                 )
  147.  
  148.     def found_terminator (self):
  149.         if self.collector:
  150.             self.collector.found_terminator()
  151.         else:
  152.             self.log_info (
  153.                 'Unexpected end-of-record for incoming request',
  154.                 'warning'
  155.                 )
  156.  
  157.     def push (self, thing):
  158.         if type(thing) == type(''):
  159.             self.outgoing.push (producers.simple_producer (thing))
  160.         else:
  161.             self.outgoing.push (thing)
  162.  
  163.     def response (self, code=200):
  164.         message = self.responses[code]
  165.         self.reply_code = code
  166.         return 'HTTP/%s %d %s' % (self.version, code, message)
  167.  
  168.     def error (self, code):
  169.         self.reply_code = code
  170.         message = self.responses[code]
  171.         s = self.DEFAULT_ERROR_MESSAGE % {
  172.             'code': code,
  173.             'message': message,
  174.             }
  175.         self['Content-Length'] = len(s)
  176.         self['Content-Type'] = 'text/html'
  177.         # make an error reply
  178.         self.push (s)
  179.         self.done()
  180.  
  181.     # can also be used for empty replies
  182.     reply_now = error
  183.  
  184.     def done (self):
  185.         "finalize this transaction - send output to the http channel"
  186.  
  187.         # ----------------------------------------
  188.         # persistent connection management
  189.         # ----------------------------------------
  190.  
  191.         #  --- BUCKLE UP! ----
  192.  
  193.         connection = string.lower (get_header (CONNECTION, self.header))
  194.  
  195.         close_it = 0
  196.         wrap_in_chunking = 0
  197.  
  198.         if self.version == '1.0':
  199.             if connection == 'keep-alive':
  200.                 if not self.has_key ('Content-Length'):
  201.                     close_it = 1
  202.                 else:
  203.                     self['Connection'] = 'Keep-Alive'
  204.             else:
  205.                 close_it = 1
  206.         elif self.version == '1.1':
  207.             if connection == 'close':
  208.                 close_it = 1
  209.             elif not self.has_key ('Content-Length'):
  210.                 if self.has_key ('Transfer-Encoding'):
  211.                     if not self['Transfer-Encoding'] == 'chunked':
  212.                         close_it = 1
  213.                 elif self.use_chunked:
  214.                     self['Transfer-Encoding'] = 'chunked'
  215.                     wrap_in_chunking = 1
  216.                 else:
  217.                     close_it = 1
  218.         elif self.version is None:
  219.             # Although we don't *really* support http/0.9 (because we'd have to
  220.             # use \r\n as a terminator, and it would just yuck up a lot of stuff)
  221.             # it's very common for developers to not want to type a version number
  222.             # when using telnet to debug a server.
  223.             close_it = 1
  224.                     
  225.         outgoing_header = producers.simple_producer (self.build_reply_header())
  226.  
  227.         if close_it:
  228.             self['Connection'] = 'close'
  229.  
  230.         if wrap_in_chunking:
  231.             outgoing_producer = producers.chunked_producer (
  232.                 producers.composite_producer (self.outgoing)
  233.                 )
  234.             # prepend the header
  235.             outgoing_producer = producers.composite_producer (
  236.                 fifo([outgoing_header, outgoing_producer])
  237.                 )
  238.         else:
  239.             # prepend the header
  240.             self.outgoing.push_front (outgoing_header)
  241.             outgoing_producer = producers.composite_producer (self.outgoing)
  242.  
  243.         # apply a few final transformations to the output
  244.         self.channel.push_with_producer (
  245.             # globbing gives us large packets
  246.             producers.globbing_producer (
  247.                 # hooking lets us log the number of bytes sent
  248.                 producers.hooked_producer (
  249.                     outgoing_producer,
  250.                     self.log
  251.                     )
  252.                 )
  253.             )
  254.  
  255.         self.channel.current_request = None
  256.  
  257.         if close_it:
  258.             self.channel.close_when_done()
  259.  
  260.     def log_date_string (self, when):
  261.         return time.strftime (
  262.             '%d/%b/%Y:%H:%M:%S ',
  263.             time.localtime(when)
  264.             ) + tz_for_log
  265.  
  266.     def log (self, bytes):
  267.         user_agent=self.get_header('user-agent')
  268.         if not user_agent: user_agent=''
  269.         referer=self.get_header('referer')
  270.         if not referer: referer=''    
  271.         self.channel.server.logger.log (
  272.             self.channel.addr[0],
  273.             ' - - [%s] "%s" %d %d "%s" "%s"\n' % (
  274. #                self.channel.addr[1],
  275.                 self.log_date_string (time.time()),
  276.                 self.request,
  277.                 self.reply_code,
  278.                 bytes,
  279.                 referer,
  280.                 user_agent
  281.                 )
  282.             )
  283.  
  284.     responses = {
  285.         100: "Continue",
  286.         101: "Switching Protocols",
  287.         200: "OK",
  288.         201: "Created",
  289.         202: "Accepted",
  290.         203: "Non-Authoritative Information",
  291.         204: "No Content",
  292.         205: "Reset Content",
  293.         206: "Partial Content",
  294.         300: "Multiple Choices",
  295.         301: "Moved Permanently",
  296.         302: "Moved Temporarily",
  297.         303: "See Other",
  298.         304: "Not Modified",
  299.         305: "Use Proxy",
  300.         400: "Bad Request",
  301.         401: "Unauthorized",
  302.         402: "Payment Required",
  303.         403: "Forbidden",
  304.         404: "Not Found",
  305.         405: "Method Not Allowed",
  306.         406: "Not Acceptable",
  307.         407: "Proxy Authentication Required",
  308.         408: "Request Time-out",
  309.         409: "Conflict",
  310.         410: "Gone",
  311.         411: "Length Required",
  312.         412: "Precondition Failed",
  313.         413: "Request Entity Too Large",
  314.         414: "Request-URI Too Large",
  315.         415: "Unsupported Media Type",
  316.         500: "Internal Server Error",
  317.         501: "Not Implemented",
  318.         502: "Bad Gateway",
  319.         503: "Service Unavailable",
  320.         504: "Gateway Time-out",
  321.         505: "HTTP Version not supported"
  322.         }
  323.  
  324.     # Default error message
  325.     DEFAULT_ERROR_MESSAGE = string.join (
  326.         ['<head>',
  327.          '<title>Error response</title>',
  328.          '</head>',
  329.          '<body>',
  330.          '<h1>Error response</h1>',
  331.          '<p>Error code %(code)d.',
  332.          '<p>Message: %(message)s.',
  333.          '</body>',
  334.          ''
  335.          ],
  336.         '\r\n'
  337.         )
  338.  
  339.  
  340. # ===========================================================================
  341. #                         HTTP Channel Object
  342. # ===========================================================================
  343.  
  344. class http_channel (asynchat.async_chat):
  345.  
  346.     # use a larger default output buffer
  347.     ac_out_buffer_size = 1<<16
  348.  
  349.     current_request = None
  350.     channel_counter = counter()
  351.  
  352.     def __init__ (self, server, conn, addr):
  353.         self.channel_number = http_channel.channel_counter.increment()
  354.         self.request_counter = counter()
  355.         asynchat.async_chat.__init__ (self, conn)
  356.         self.server = server
  357.         self.addr = addr
  358.         self.set_terminator ('\r\n\r\n')
  359.         self.in_buffer = ''
  360.         self.creation_time = int (time.time())
  361.         self.check_maintenance()
  362.  
  363.     def __repr__ (self):
  364.         ar = asynchat.async_chat.__repr__(self)[1:-1]
  365.         return '<%s channel#: %s requests:%s>' % (
  366.             ar,
  367.             self.channel_number,
  368.             self.request_counter
  369.             )
  370.  
  371.     # Channel Counter, Maintenance Interval...
  372.     maintenance_interval = 500
  373.  
  374.     def check_maintenance (self):
  375.         if not self.channel_number % self.maintenance_interval:
  376.             self.maintenance()
  377.  
  378.     def maintenance (self):
  379.         self.kill_zombies()
  380.  
  381.     # 30-minute zombie timeout.  status_handler also knows how to kill zombies.
  382.     zombie_timeout = 30 * 60
  383.  
  384.     def kill_zombies (self):
  385.         now = int (time.time())
  386.         for channel in asyncore.socket_map.values():
  387.             if channel.__class__ == self.__class__:
  388.                 if (now - channel.creation_time) > channel.zombie_timeout:
  389.                     channel.close()
  390.  
  391.     # --------------------------------------------------
  392.     # send/recv overrides, good place for instrumentation.
  393.     # --------------------------------------------------
  394.     
  395.     # this information needs to get into the request object,
  396.     # so that it may log correctly.
  397.     def send (self, data):
  398.         result = asynchat.async_chat.send (self, data)
  399.         self.server.bytes_out.increment (len(data))
  400.         return result
  401.  
  402.     def recv (self, buffer_size):
  403.         try:
  404.             result = asynchat.async_chat.recv (self, buffer_size)
  405.             self.server.bytes_in.increment (len(result))
  406.             return result
  407.         except MemoryError:
  408.             # --- Save a Trip to Your Service Provider ---
  409.             # It's possible for a process to eat up all the memory of
  410.             # the machine, and put it in an extremely wedged state,
  411.             # where medusa keeps running and can't be shut down.  This
  412.             # is where MemoryError tends to get thrown, though of
  413.             # course it could get thrown elsewhere.
  414.             sys.exit ("Out of Memory!")
  415.  
  416.     def handle_error (self):
  417.         t, v = sys.exc_info()[:2]
  418.         if t is SystemExit:
  419.             raise t, v
  420.         else:
  421.             asynchat.async_chat.handle_error (self)
  422.  
  423.     def log (self, *args):
  424.         pass
  425.  
  426.     # --------------------------------------------------
  427.     # async_chat methods
  428.     # --------------------------------------------------
  429.  
  430.     def collect_incoming_data (self, data):
  431.         if self.current_request:
  432.             # we are receiving data (probably POST data) for a request
  433.             self.current_request.collect_incoming_data (data)
  434.         else:
  435.             # we are receiving header (request) data
  436.             self.in_buffer = self.in_buffer + data
  437.  
  438.     def found_terminator (self):
  439.         if self.current_request:
  440.             self.current_request.found_terminator()
  441.         else:
  442.             header = self.in_buffer
  443.             self.in_buffer = ''
  444.             lines = string.split (header, '\r\n')
  445.  
  446.             # --------------------------------------------------
  447.             # crack the request header
  448.             # --------------------------------------------------
  449.  
  450.             while lines and not lines[0]:
  451.                 # as per the suggestion of http-1.1 section 4.1, (and
  452.                 # Eric Parker <eparker@zyvex.com>), ignore a leading
  453.                 # blank lines (buggy browsers tack it onto the end of
  454.                 # POST requests)
  455.                 lines = lines[1:]
  456.  
  457.             if not lines:
  458.                 self.close_when_done()
  459.                 return
  460.  
  461.             request = lines[0]
  462.             command, uri, version = crack_request (request)
  463.             header = join_headers (lines[1:])
  464.  
  465.             r = http_request (self, request, command, uri, version, header)
  466.             self.request_counter.increment()
  467.             self.server.total_requests.increment()
  468.  
  469.             if command is None:
  470.                 self.log_info('Bad HTTP request: %s' % request, 'error')
  471.                 r.error(400)
  472.                 return
  473.  
  474.             # --------------------------------------------------
  475.             # handler selection and dispatch
  476.             # --------------------------------------------------
  477.             for h in self.server.handlers:
  478.                 if h.match (r):
  479.                     try:
  480.                         self.current_request = r
  481.                         # This isn't used anywhere.
  482.                         # r.handler = h # CYCLE
  483.                         h.handle_request (r)
  484.                     except:
  485.                         self.server.exceptions.increment()
  486.                         (file, fun, line), t, v, tbinfo = asyncore.compact_traceback()
  487.                         self.log_info(
  488.                                 'Server Error: %s, %s: file: %s line: %s' % (t,v,file,line),
  489.                                 'error')
  490.                         try:
  491.                             r.error (500)
  492.                         except:
  493.                             pass
  494.                     return
  495.  
  496.             # no handlers, so complain
  497.             r.error (404)
  498.  
  499.     def writable (self):
  500.         # this is just the normal async_chat 'writable', here for comparison
  501.         return self.ac_out_buffer or len(self.producer_fifo)
  502.  
  503.     def writable_for_proxy (self):
  504.         # this version of writable supports the idea of a 'stalled' producer
  505.         # [i.e., it's not ready to produce any output yet] This is needed by
  506.         # the proxy, which will be waiting for the magic combination of
  507.         # 1) hostname resolved
  508.         # 2) connection made
  509.         # 3) data available.
  510.         if self.ac_out_buffer:
  511.             return 1
  512.         elif len(self.producer_fifo):
  513.             p = self.producer_fifo.first()
  514.             if hasattr (p, 'stalled'):
  515.                 return not p.stalled()
  516.             else:
  517.                 return 1
  518.  
  519. # ===========================================================================
  520. #                         HTTP Server Object
  521. # ===========================================================================
  522.  
  523. class http_server (asyncore.dispatcher):
  524.  
  525.     SERVER_IDENT = 'HTTP Server (V%s)' % VERSION_STRING
  526.  
  527.     channel_class = http_channel
  528.  
  529.     def __init__ (self, ip, port, resolver=None, logger_object=None):
  530.         self.ip = ip
  531.         self.port = port
  532.         asyncore.dispatcher.__init__ (self)
  533.         self.create_socket (socket.AF_INET, socket.SOCK_STREAM)
  534.  
  535.         self.handlers = []
  536.  
  537.         if not logger_object:
  538.             logger_object = logger.file_logger (sys.stdout)
  539.  
  540.         self.set_reuse_addr()
  541.         self.bind ((ip, port))
  542.  
  543.         # lower this to 5 if your OS complains
  544.         self.listen (1024)
  545.  
  546.         host, port = self.socket.getsockname()
  547.         if not ip:
  548.             self.log_info('Computing default hostname', 'warning')
  549.             ip = socket.gethostbyname (socket.gethostname())
  550.         try:
  551.             self.server_name = socket.gethostbyaddr (ip)[0]
  552.         except socket.error:
  553.             self.log_info('Cannot do reverse lookup', 'warning')
  554.             self.server_name = ip       # use the IP address as the "hostname"
  555.  
  556.         self.server_port = port
  557.         self.total_clients = counter()
  558.         self.total_requests = counter()
  559.         self.exceptions = counter()
  560.         self.bytes_out = counter()
  561.         self.bytes_in  = counter()
  562.  
  563.         if not logger_object:
  564.             logger_object = logger.file_logger (sys.stdout)
  565.  
  566.         if resolver:
  567.             self.logger = logger.resolving_logger (resolver, logger_object)
  568.         else:
  569.             self.logger = logger.unresolving_logger (logger_object)
  570.  
  571.         self.log_info (
  572.             'Medusa (V%s) started at %s'
  573.             '\n\tHostname: %s'
  574.             '\n\tPort:%d'
  575.             '\n' % (
  576.                 VERSION_STRING,
  577.                 time.ctime(time.time()),
  578.                 self.server_name,
  579.                 port,
  580.                 )
  581.             )
  582.  
  583.     def writable (self):
  584.         return 0
  585.  
  586.     def handle_read (self):
  587.         pass
  588.  
  589.     def readable (self):
  590.         return self.accepting
  591.  
  592.     def handle_connect (self):
  593.         pass
  594.  
  595.     def handle_accept (self):
  596.         self.total_clients.increment()
  597.         try:
  598.             conn, addr = self.accept()
  599.         except socket.error:
  600.             # linux: on rare occasions we get a bogus socket back from
  601.             # accept.  socketmodule.c:makesockaddr complains that the
  602.             # address family is unknown.  We don't want the whole server
  603.             # to shut down because of this.
  604.             self.log_info ('warning: server accept() threw an exception', 'warning')
  605.             return
  606.         except TypeError:
  607.             # unpack non-sequence.  this can happen when a read event
  608.             # fires on a listening socket, but when we call accept()
  609.             # we get EWOULDBLOCK, so dispatcher.accept() returns None.
  610.             # Seen on FreeBSD3.
  611.             self.log_info ('warning: server accept() threw EWOULDBLOCK', 'warning')
  612.             return
  613.  
  614.         self.channel_class (self, conn, addr)
  615.  
  616.     def install_handler (self, handler, back=0):
  617.         if back:
  618.             self.handlers.append (handler)
  619.         else:
  620.             self.handlers.insert (0, handler)
  621.  
  622.     def remove_handler (self, handler):
  623.         self.handlers.remove (handler)
  624.  
  625.     def status (self):
  626.         def nice_bytes (n):
  627.             return string.join (status_handler.english_bytes (n))
  628.  
  629.         handler_stats = filter (None, map (maybe_status, self.handlers))
  630.         
  631.         if self.total_clients:
  632.             ratio = self.total_requests.as_long() / float(self.total_clients.as_long())
  633.         else:
  634.             ratio = 0.0
  635.         
  636.         return producers.composite_producer (
  637.             fifo ([producers.lines_producer (
  638.                 ['<h2>%s</h2>'                            % self.SERVER_IDENT,
  639.                 '<br>Listening on: <b>Host:</b> %s'        % self.server_name,
  640.                 '<b>Port:</b> %d'                        % self.port,
  641.                  '<p><ul>'
  642.                  '<li>Total <b>Clients:</b> %s'            % self.total_clients,
  643.                  '<b>Requests:</b> %s'                    % self.total_requests,
  644.                  '<b>Requests/Client:</b> %.1f'            % (ratio),
  645.                  '<li>Total <b>Bytes In:</b> %s'    % (nice_bytes (self.bytes_in.as_long())),
  646.                  '<b>Bytes Out:</b> %s'                % (nice_bytes (self.bytes_out.as_long())),
  647.                  '<li>Total <b>Exceptions:</b> %s'        % self.exceptions,
  648.                  '</ul><p>'
  649.                  '<b>Extension List</b><ul>',
  650.                  ])] + handler_stats + [producers.simple_producer('</ul>')]
  651.                   )
  652.             )
  653.  
  654. def maybe_status (thing):
  655.     if hasattr (thing, 'status'):
  656.         return thing.status()
  657.     else:
  658.         return None
  659.  
  660. CONNECTION = regex.compile ('Connection: \(.*\)', regex.casefold)
  661.  
  662. # merge multi-line headers
  663. # [486dx2: ~500/sec]
  664. def join_headers (headers):
  665.     r = []
  666.     for i in range(len(headers)):
  667.         if headers[i][0] in ' \t':    
  668.             r[-1] = r[-1] + headers[i][1:]
  669.         else:
  670.             r.append (headers[i])
  671.     return r
  672.  
  673. def get_header (head_reg, lines, group=1):
  674.     for line in lines:
  675.         if head_reg.match (line) == len(line):
  676.             return head_reg.group(group)
  677.     return ''
  678.  
  679. REQUEST = re.compile ('([^ ]+) (?:[^ :?#]+://[^ ?#/]*)?([^ ]+)(( HTTP/([0-9.]+))$|$)')
  680.  
  681. def crack_request (r):
  682.     m = REQUEST.match(r)
  683.     if m is not None:
  684.         return string.lower (m.group (1)), m.group(2), m.group(5)
  685.     else:
  686.         return None, None, None    
  687.  
  688. class fifo:
  689.     def __init__ (self, list=None):
  690.         if not list:
  691.             self.list = []
  692.         else:
  693.             self.list = list
  694.         
  695.     def __len__ (self):
  696.         return len(self.list)
  697.  
  698.     def first (self):
  699.         return self.list[0]
  700.  
  701.     def push_front (self, object):
  702.         self.list.insert (0, object)
  703.  
  704.     def push (self, data):
  705.         self.list.append (data)
  706.  
  707.     def pop (self):
  708.         if self.list:
  709.             result = self.list[0]
  710.             del self.list[0]
  711.             return (1, result)
  712.         else:
  713.             return (0, None)
  714.  
  715. def compute_timezone_for_log ():
  716.     if time.daylight:
  717.         tz = time.altzone
  718.     else:
  719.         tz = time.timezone
  720.     if tz > 0:
  721.         neg = 1
  722.     else:
  723.         neg = 0
  724.         tz = -tz
  725.     h, rem = divmod (tz, 3600)
  726.     m, rem = divmod (rem, 60)
  727.     if neg:
  728.         return '-%02d%02d' % (h, m)
  729.     else:
  730.         return '+%02d%02d' % (h, m)
  731.  
  732. # if you run this program over a TZ change boundary, this will be invalid.
  733. tz_for_log = compute_timezone_for_log()
  734.  
  735. if __name__ == '__main__':
  736.     import sys
  737.     if len(sys.argv) < 2:
  738.         print 'usage: %s <root> <port>' % (sys.argv[0])
  739.     else:
  740.         import monitor
  741.         import filesys
  742.         import default_handler
  743.         import status_handler
  744.         import ftp_server
  745.         import chat_server
  746.         import resolver
  747.         import logger
  748.         rs = resolver.caching_resolver ('127.0.0.1')
  749.         lg = logger.file_logger (sys.stdout)
  750.         ms = monitor.secure_monitor_server ('fnord', '127.0.0.1', 9999)
  751.         fs = filesys.os_filesystem (sys.argv[1])
  752.         dh = default_handler.default_handler (fs)
  753.         hs = http_server ('', string.atoi (sys.argv[2]), rs, lg)
  754.         hs.install_handler (dh)
  755.         ftp = ftp_server.ftp_server (
  756.             ftp_server.dummy_authorizer(sys.argv[1]),
  757.             port=8021,
  758.             resolver=rs,
  759.             logger_object=lg
  760.             )
  761.         cs = chat_server.chat_server ('', 7777)
  762.         sh = status_handler.status_extension([hs,ms,ftp,cs,rs])
  763.         hs.install_handler (sh)
  764.         if ('-p' in sys.argv):
  765.             def profile_loop ():
  766.                 try:
  767.                     asyncore.loop()
  768.                 except KeyboardInterrupt:
  769.                     pass
  770.             import profile
  771.             profile.run ('profile_loop()', 'profile.out')
  772.         else:
  773.             asyncore.loop()
  774.